package com.orcamento.entity;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.AttributeOverride;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.joda.time.DateTime;

import com.orcamento.entity.enums.Month;
import com.orcamento.util.CalendarUtil;
import com.orcamento.util.MathUtil;
import com.orcamento.util.Money;

@Entity
@Table(name="Fundo")
public class Fund {

	@Id
	@SequenceGenerator(name="fund_id", sequenceName="fund_id")
	@GeneratedValue(generator="fund_id", strategy=GenerationType.AUTO)
	@Column(name="IDFundo")
	private Long id;
	@Embedded
	@AttributeOverride(name="value", column=@Column(name="FoValorInicial", nullable=false))
	private Money startMoney;
	@Column(name="BoAtivo", nullable=false, columnDefinition="boolean default true")
	private Boolean status;

	@OneToOne
	@JoinColumn(name="IDEvento", referencedColumnName="IDEvento", nullable=false)
	private Event event = new Event();
	
	@ManyToOne(targetEntity=Account.class)
	@JoinColumn(name="IDConta", referencedColumnName="IDPatrimonio", nullable=false)
	private Account account = new Account();
	
	@OneToMany(mappedBy="fund")
	@Cascade({CascadeType.ALL})
	private List<Gain> gains = new ArrayList<Gain>();
	@OneToMany(mappedBy="fund")
	@Cascade({CascadeType.ALL})
	private List<Spent> spents = new ArrayList<Spent>();
	@OneToMany(mappedBy="origin")
	private List<Exchange> origns = new ArrayList<Exchange>();
	@OneToMany(mappedBy="destiny")
	private List<Exchange> destinies = new ArrayList<Exchange>();
	
	@Transient
	private Money startBalance;
	@Transient
	private Money finalBalance;

	public void generateStartBalance(Integer year, Month month) {
		year = correctYearIfNecessary(year, month);
		DateTime startDate = CalendarUtil.toDate(month.previous().maxDays, month.previous().id, year, 23, 59, 59);
		Money money = startMoney;
		
		for( Gain gain : gains ) {
			if( startDate.isAfter( gain.getReceivedDay() ) ) {
				money = money.plus( gain.getValue() );
			}
		}
		
		for( Spent spent : spents ) {
			if( startDate.isAfter( spent.getSpentDay() ) ) {
				money = money.minus( spent.getValue() );
			}
		}
		
		for( Exchange originExchange : origns ) {
			if( startDate.isAfter( originExchange.getDate() ) ) {
				money = money.minus( originExchange.getValue() );
			}
		}
		
		for( Exchange destinyExchange : destinies ) {
			if( startDate.isAfter( destinyExchange.getDate() ) ) {
				money = money.plus( destinyExchange.getValue() );
			}
		}
		
		this.startBalance = money;
	}
	
	private int correctYearIfNecessary(Integer year, Month month) {
		return month.getName().equals( Month.JANUARY.getName() ) ? year-1 : year;
	}
	
	public void generateFinalBalance(Integer year, Month month) {
		DateTime endDate = CalendarUtil.toDate(month.maxDays, month.id, year, 23, 59, 59);
		Money money = startMoney;
		
		for( Gain gain : gains ) {
			if( endDate.isAfter( gain.getReceivedDay() ) ) {
				money = money.plus( gain.getValue() );
			}
		}
		
		for( Spent spent : spents ) {
			if( endDate.isAfter( spent.getSpentDay() ) ) {
				money = money.minus( spent.getValue() ); 
			}
		}

		for( Exchange originExchange : origns ) {
			if( endDate.isAfter( originExchange.getDate() ) ) {
				money = money.minus( originExchange.getValue() );
			}
		}
		
		for( Exchange destinyExchange : destinies ) {
			if( endDate.isAfter( destinyExchange.getDate() ) ) {
				money = money.plus( destinyExchange.getValue() );
			}
		}
		
		this.finalBalance = money;
	}

	public Money getTotalSpentsBetweenDates(DateTime startDate, DateTime endDate) {
		Money totalSpent = new Money();
		for( Spent spent : spents ) {
			if( startDate.isBefore(spent.getSpentDay()) && endDate.isAfter(spent.getSpentDay()) ) {
				totalSpent = totalSpent.plus( spent.getValue() );
			}
		}
		return totalSpent;
	}

	public Money getTotalGainsBetweenDates(DateTime startDate, DateTime endDate) {
		Money totalGain = new Money();
		for( Gain gain : gains ) {
			if( startDate.isBefore(gain.getReceivedDay()) && endDate.isAfter(gain.getReceivedDay()) ) {
				totalGain = totalGain.plus( gain.getValue() );
			}
		}
		return totalGain;
	}
	
	public Money getEvolution() {
		return finalBalance.minus( startBalance );
	}

	public String getEvolutionPercent() {
		if( startBalance.isZero() ) {
			return "---";
		}
		return MathUtil.getMoneyPercentage(getEvolution(), startBalance);
	}
	
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	public Event getEvent() {
		return event;
	}
	public void setEvent(Event event) {
		this.event = event;
	}
	public Money getStartMoney() {
		return startMoney;
	}
	public void setStartMoney(Money startMoney) {
		this.startMoney = startMoney;
	}
	public List<Gain> getGains() {
		return gains;
	}

	public void setGains(List<Gain> gains) {
		this.gains = gains;
	}

	public List<Spent> getSpents() {
		return spents;
	}

	public void setSpents(List<Spent> spents) {
		this.spents = spents;
	}

	public Money getStartBalance() {
		return startBalance;
	}

	public void setStartBalance(Money startBalance) {
		this.startBalance = startBalance;
	}

	public Money getFinalBalance() {
		return finalBalance;
	}

	public void setFinalBalance(Money finalBalance) {
		this.finalBalance = finalBalance;
	}
	public Boolean getStatus() {
		return status;
	}
	public void setStatus(Boolean status) {
		this.status = status;
	}
	public List<Exchange> getDestinies() {
		return destinies;
	}
	public void setDestinies(List<Exchange> destinies) {
		this.destinies = destinies;
	}
	public List<Exchange> getOrigns() {
		return origns;
	}
	public void setOrigns(List<Exchange> origns) {
		this.origns = origns;
	}
	public Account getAccount() {
		return account;
	}
	public void setAccount(Account account) {
		this.account = account;
	}
	
}
